# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""" This is a line drawing script for Touchbot II

By using its rather long set of parameters, this script can generate
a wide array of different straight-line based gestures on the Touchbot II.

The gestures take in two points (start and end) which consist of:
    x, y coordinates in the range from 0.0->1.0
    angle in degrees
    finger distance in mm that specifies how far apart the hand should open
Which fingers the robot should extend as a list of 4 booleans
    Top right, Top Left, Bottom Left, Bottom Right
A speed in approximately mm/s
Which kind of line to do which should be either "swipe" or "basic"

The robot will then linearly interpolate between those two points

Examples:
    For a two finger scroll motion you might do something like:
    python line.py link.p 0.5 0.1 0 40 0.5 0.9 0 40 0 1 0 1 100 swipe

    For a human-like pinch zoom motion
    python line.py lumpy.p 0.5 0.5 30 45 0.5 0.5 27 20 1 0 1 0 75 basic
"""

import sys
import time
import traceback
from collections import namedtuple

from touchbotII import Touchbot, Device, PositionArg


try:
    # Load the device spec
    device = Device(sys.argv[1])
    start = PositionArg(*[float(v) for v in sys.argv[2:6]])
    end = PositionArg(*[float(v) for v in sys.argv[6:10]])
    fingers = [int(arg) for arg in sys.argv[10:14]]
    speed = float(sys.argv[14])
    is_swipe = bool(sys.argv[15] == 'swipe')
    is_fling = bool(sys.argv[15] == 'fling')
    delay = 0.0
    if len(sys.argv) > 16:
        delay = float(sys.argv[16])
except:
    traceback.print_exc()
    print (('Usage: python %s device.p start end finger_states speed ' +
            '[swipe|basic|fling] [delay]') % __file__)
    print '     * start and end: formatted as x y angle finger_distance'
    print '     * finger_states: a list of 4 values (1s and 0s)'
    print '     * speed: a decimal value for the speed in mm/s'
    print '     * which kind of line to draw: either "swipe" or "basic"'
    print
    print 'For a two finger scroll motion you might do something like:'
    print ('python %s link.p 0.5 0.1 0 40 0.5 0.9 0 40 0 1 0 1 100 swipe' %
           __file__)
    print
    print 'For a human-like pinch zoom motion'
    print ('python %s lumpy.p 0.5 0.5 30 45 0.5 0.5 27 20 1 0 1 0 75 basic' %
          __file__)
    sys.exit(-1)

print 'Executing the line defined by:'
print '\tStart:     %s' % str(start)
print '\tEnd:       %s' % str(end)
print '\tFingers:   %s' % str(fingers)
print '\tSpeed:     %f' % speed
print '\tIs Swipe?: %s' % str(is_swipe)
print '\tIs Fling?: %s' % str(is_fling)
print '\tDelay:     %f' % delay

# Connect to the robot and configure the profile
bot = Touchbot()
prof = bot.GetCurrentProfile()
prof.speed = speed
prof.straight = Touchbot.STRAIGHT_INTERPOLATION
bot.SetProfileData(prof)

if not (bot.IsLegalRelativeCoordinate((start.x, start.y)) and
        bot.IsLegalRelativeCoordinate((end.x, end.y))):
    print ('All coordinates must fall in the range of %s' %
           str(Touchbot.RELATIVE_COORDINATE_RANGE))
    sys.exit(-1)
if not bot.IsLegalFingerList(fingers):
    print 'Your finger list must contain exactly 4 values, each either 1 or 0'
    sys.exit(-1)
if not bot.IsLegalSpeed(speed):
    print 'The speed %f is not acceptable for this robot' % speed
    sys.exit(-1)


# Convert the point specifications into something the robot understands
abs_start = device.RelativePosToAbsolutePos((start.x, start.y),
                                            angle=start.angle)
abs_end = device.RelativePosToAbsolutePos((end.x, end.y),
                                          angle=end.angle)

# Offset the position to center the finger if it's only using one
abs_start = bot.CenterIfSingleFinger(fingers, abs_start, start.finger_distance)
abs_end = bot.CenterIfSingleFinger(fingers, abs_end, end.finger_distance)

# Go to the starting point
bot.SetFingerStates([0, 0, 0, 0])
bot.SetCartesian(abs_start, start.finger_distance, blocking=True)

# Sort out what to do with the fingers as it moves depending on if it is a
# swipe or a regular, basic line.
if is_swipe:
    # start moving to end point
    bot.SetCartesian(abs_end, end.finger_distance, blocking=False)

    distance_to_travel = abs_start.CartesianDistanceFrom(abs_end)
    distance_traveled = 0.0
    # Wait until 1/4 of the distance has been traveled to extend the fingers
    # This allows the hand to get up to speed before touching the pad
    while distance_traveled < 0.25 * distance_to_travel:
        curr_pos = bot.GetCurrentPosition()
        distance_traveled = curr_pos.CartesianDistanceFrom(abs_start)
    bot.SetFingerStates(fingers)

    # Raise the fingers after only 3/4 of the distance has been traveled
    while distance_traveled < 0.75 * distance_to_travel:
        curr_pos = bot.GetCurrentPosition()
        distance_traveled = curr_pos.CartesianDistanceFrom(abs_start)
    bot.SetFingerStates([0, 0, 0, 0])
elif is_fling:
    # put fingers down and wait for delay
    bot.SetFingerStates(fingers)
    time.sleep(delay)
    bot.SetCartesian(abs_end, end.finger_distance, blocking=False)

    # Move to end point
    distance_to_travel = abs_start.CartesianDistanceFrom(abs_end)
    distance_traveled = 0.0
    while distance_traveled < 0.75 * distance_to_travel:
        curr_pos = bot.GetCurrentPosition()
        distance_traveled = curr_pos.CartesianDistanceFrom(abs_start)
    bot.SetFingerStates([0, 0, 0, 0])
else:
    # put fingers down and wait for delay
    bot.SetFingerStates(fingers)
    time.sleep(delay)
    # Move to end point
    bot.SetCartesian(abs_end, end.finger_distance, blocking=True)
    bot.SetFingerStates([0, 0, 0, 0])
